package com.bolt.support.base.service;

import com.bolt.common.collection.CollectionUtil;
import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.reflect.ReflectionUtil;
import com.bolt.common.utils.ArrayUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.convention.data.DefaultPageInfo;
import com.bolt.convention.data.PageInfo;
import com.bolt.convention.data.Record;
import com.bolt.convention.exception.ServiceException;
import com.bolt.support.query.nativequery.NQuery;
import com.bolt.support.query.nativequery.parall.ParallelQuery;
import com.bolt.support.query.nativequery.parall.ParallelQueryConfig;
import com.bolt.support.query.nativequery.parall.ParallelQueryResult;
import com.bolt.support.spring.jpa.entity.PersistableEntity;
import com.bolt.support.spring.jpa.filter.GroupPropertyFilter;
import com.bolt.support.spring.jpa.filter.IFilterBuilder;
import com.bolt.support.spring.jpa.jpql.JPAQueryFactory;
import com.bolt.support.spring.jpa.repository.BasicJpaRepository;
import com.bolt.support.spring.jpa.specification.Specifications;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.*;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.*;

/**
 * Created by Administrator on 2017/9/28.
 */
public abstract class BaseService<T extends PersistableEntity, ID extends Serializable> implements IBaseService<T, ID>, INativeQuery {
    //日志对象
    protected final Log logger = LogFactory.getLog(getClass());

    //默认忽略
    private static final String[] DEFAULT_IGNORE_PROPERTY = {"id", "createDate"};

    //持久管理对象
    @PersistenceContext
    protected EntityManager entityManager;

    @Autowired(required = false)
    protected JPAQueryFactory jpaQueryFactory;

    @Autowired(required = false)
    protected NQuery nQuery;
    /**
     * 泛型对应的Class定义
     */
    protected Class<T> entityClass;

    protected String entityName;


    //返回数据操作对象
    abstract protected BasicJpaRepository<T, ID> getRepository();

    @SuppressWarnings("unchecked")
    public BaseService() {
        // 通过反射取得Entity的Class.
        try {
            Object genericClz = getClass().getGenericSuperclass();
            if (genericClz instanceof ParameterizedType) {
                entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
                        .getActualTypeArguments()[0];
                Entity annotation = entityClass.getAnnotation(Entity.class);
                this.entityName = annotation.name();
                if (StrUtil.isBlank(entityName)) {
                    this.entityName = entityClass.getSimpleName();
                }
            }
        } catch (Exception e) {
            logger.error("error detail:", e);
        }
    }


    public Class<T> getEntityClass() {
        return entityClass;
    }

    protected Session getSession() {
        Session session = entityManager.unwrap(Session.class);
        return session;
    }

    /**
     * 创建数据保存数据之前额外操作回调方法 默认为空逻辑，子类根据需要覆写添加逻辑即可
     *
     * @param entity 待创建数据对象
     */
    protected void preInsert(T entity) {

    }

    /**
     * 更新数据保存数据之前额外操作回调方法 默认为空逻辑，子类根据需要覆写添加逻辑即可
     *
     * @param entity 待更新数据对象
     */
    protected void preUpdate(T entity) {

    }


    /**
     * 数据保存操作
     *
     * @param entity
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public T save(T entity) {
        if (entity.isNew()) {
            preInsert(entity);
        } else {
            preUpdate(entity);
        }
        return getRepository().save(entity);
    }

    /**
     * 批量数据保存操作 其实现只是简单循环集合每个元素调用
     * 因此并无实际的Batch批量处理，如果需要数据库底层批量支持请自行实现
     *
     * @param entities 待批量操作数据集合
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<T> save(Iterable<T> entities) {
        List<T> result = new ArrayList<T>();
        if (entities == null) {
            return result;
        }
        for (T entity : entities) {
            entityManager.persist(entity);
            result.add(entity);
        }
        entityManager.flush();
        entityManager.clear();
        return result;
    }

    /**
     * 批量插入数据
     *
     * @param entities
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<T> batchInsert(List<T> entities, int batchSize) {
        Iterator<T> iterator = entities.iterator();
        int index = 0;
        while (iterator.hasNext()) {
            entityManager.persist(iterator.next());
            index++;
            if (index % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }
        if (index % batchSize != 0) {
            entityManager.flush();
            entityManager.clear();
        }
        return entities;
    }

    /**
     * 批量修改数据
     *
     * @param entities
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<T> batchUpdate(List<T> entities, int batchSize) {
        Iterator<T> iterator = entities.iterator();
        int index = 0;
        while (iterator.hasNext()) {
            entityManager.merge(iterator.next());
            index++;
            if (index % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }
        if (index % batchSize != 0) {
            entityManager.flush();
            entityManager.clear();
        }
        return entities;
    }


    /**
     * 基于主键查询单一数据对象
     *
     * @param id
     * @return
     */
    @Override
    public Optional<T> findOne(ID id) {
        return getRepository().findById(id);
    }

    /**
     * 基于动态组合条件对象和排序定义查询数据集合
     *
     * @param filter
     * @return
     */
    @Override
    @Deprecated
    @Transactional(readOnly = true)
    public Optional<T> findOne(GroupPropertyFilter filter) {
        return getRepository().findOne(filter);
    }


    @Override
    @Transactional(readOnly = true)
    public Optional<T> findOne(IFilterBuilder<T> builder) {
        GroupPropertyFilter filter = builder.build(IFilterBuilder.toFilter());
        return getRepository().findOne(filter);
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> findAll(GroupPropertyFilter filter) {
        return getRepository().findAll(filter);
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> findAll(IFilterBuilder<T> builder) {
        GroupPropertyFilter filter = builder.build(IFilterBuilder.toFilter());
        return getRepository().findAll(filter);
    }


    @Override
    @Transactional(readOnly = true)
    public List<T> findLimit(IFilterBuilder<T> builder, Sort sort, int limit) {
        GroupPropertyFilter filter = builder.build(IFilterBuilder.toFilter());
        return findLimit(filter, sort, limit);
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> findLimit(GroupPropertyFilter filter, Sort sort, int limit) {
        return findLimit(Specifications.get(filter), sort, limit);
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> findLimit(@Nullable Specification<T> spec, Sort sort, int limit) {
        return getRepository().findLimit(spec, sort, limit);
    }

    /**
     * 基于主键集合查询集合数据对象
     *
     * @param ids 主键集合
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public List<T> findAll(final Iterable<ID> ids) {
        Iterable<T> it = this.getRepository().findAllById(ids);
        return IteratorUtil.toList(it.iterator());
    }

    /**
     * 基于主键集合查询集合数据对象
     *
     * @param ids 主键集合
     * @return
     */
    @Override
    @Transactional(readOnly = true)
    public List<T> findAll(final ID... ids) {
        Assert.isTrue(ids != null && ids.length > 0, "必须提供有效查询主键集合");
        Specification<T> spec = new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                @SuppressWarnings("rawtypes")
                Path expression = root.get("id");
                return expression.in(ids);
            }
        };
        return this.getRepository().findAll(spec);
    }

    /**
     * 查询全部数据对象
     *
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public List<T> findAll() {
        Iterable<T> it = getRepository().findAll();
        return IteratorUtil.toList(it.iterator());
    }

    @Override
    public List<T> findAll(@Nullable Specification<T> spec, Sort sort) {
        return getRepository().findAll(spec, sort);
    }


    /**
     * 数据删除操作
     *
     * @param entity 待操作数据
     */

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(T entity) {
        getRepository().delete(entity);
    }

    /**
     * 批量数据删除操作 其实现只是简单循环集合每个元素调用
     * 因此并无实际的Batch批量处理，如果需要数据库底层批量支持请自行实现
     *
     * @param entities 待批量操作数据集合
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Iterable<T> entities) {
        for (T entity : entities) {
            delete(entity);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(ID id) {
        Optional<T> entity = findOne(id);
        if (entity.isPresent()) {
            delete(entity.get());
        }
    }

    @Override
    @Transactional(readOnly = true)
    public PageInfo<T> findPage(Pageable pageable, GroupPropertyFilter filters) {
        Page<T> page = getRepository().findPage(pageable, filters);
        return new DefaultPageInfo(pageable.getPageNumber() + 1, pageable.getPageSize(), page.getContent(), page.getTotalElements());
    }

    @Override
    @Transactional(readOnly = true)
    public PageInfo<T> findPage(Pageable pageable, @Nullable Specification<T> spec) {
        Page<T> page = getRepository().findAll(spec, pageable);
        return new DefaultPageInfo(pageable.getPageNumber() + 1, pageable.getPageSize(), page.getContent(), page.getTotalElements());
    }

    @Override
    @Transactional(readOnly = true)
    public PageInfo<T> findPage(Pageable pageable, IFilterBuilder<T> builder) {
        GroupPropertyFilter filter = builder.build(IFilterBuilder.toFilter());
        Page<T> page = getRepository().findPage(pageable, filter);
        return new DefaultPageInfo(pageable.getPageNumber() + 1, pageable.getPageSize(), page.getContent(), page.getTotalElements());
    }

    /**
     * 查询符合条件的行数
     *
     * @param filters
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public long findCount(GroupPropertyFilter filters) {
        return getRepository().count(Specifications.get(filters));
    }

    @Override
    @Transactional(readOnly = true)
    public long findCount(IFilterBuilder<T> builder) {
        GroupPropertyFilter filter = builder.build(IFilterBuilder.toFilter());
        return findCount(filter);
    }


    @Override
    public long findCount(@Nullable Specification<T> spec) {
        return getRepository().count(spec);
    }

    /**
     * 更新过滤不更新属性
     *
     * @param entity
     * @param ignoreProperties
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public T update(T entity, String... ignoreProperties) {
        Assert.notNull(entity);
        if (!this.getRepository().isManaged(entity))
            throw new IllegalArgumentException("Entity must not be managed");
        Optional<T> newObj = findOne(this.getRepository().getIdentifier(entity));
        newObj.ifPresent(one -> {
            copyProperties((Object) entity, (Object) one, (String[]) ArrayUtil.addAll(ignoreProperties, DEFAULT_IGNORE_PROPERTY));
        });
        return newObj.get();
    }

    /**
     * 可以做为VO向PO转存的简单实现方法
     *
     * @param vo               游离对象或前端转来的VO
     * @param entity           PO实体
     * @param ignoreProperties 忽略处理的属性名称
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public T update(Object vo, T entity, String... ignoreProperties) {
        if (!this.getRepository().isManaged(entity))
            throw new IllegalArgumentException("Entity must be managed");
        if (vo != null) {
            copyProperties(vo, (Object) entity, (String[]) ArrayUtil.addAll(ignoreProperties, DEFAULT_IGNORE_PROPERTY));
            return this.getRepository().save(entity);
        }
        return this.getRepository().save(entity);
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private void copyProperties(Object source, Object target, String[] ignoreProperties) {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(target.getClass());
        List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null;

        for (PropertyDescriptor targetPd : targetPds) {
            if (targetPd.getWriteMethod() != null &&
                    (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) {
                PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null && sourcePd.getReadMethod() != null) {
                    try {
                        Method readMethod = sourcePd.getReadMethod();
                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                            readMethod.setAccessible(true);
                        }
                        Object sourceValue = readMethod.invoke(source);
                        Object targetValue = readMethod.invoke(target, new Object[0]);
                        Object tempValue;
                        if ((sourceValue != null) && (targetValue != null) && ((targetValue instanceof Collection))) {
                            tempValue = (Collection) targetValue;
                            ((Collection) tempValue).clear();
                            ((Collection) tempValue).addAll((Collection) sourceValue);
                        } else {
                            Method writeMethod = targetPd.getWriteMethod();
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            writeMethod.invoke(target, sourceValue);
                        }
                    } catch (Throwable ex) {
                        throw new FatalBeanException("Could not copy properties from source to target", ex);
                    }
                }
            }
        }
    }

    /**
     * 供子类调用的关联对象关联关系操作辅助方法
     *
     * @param entity               当前关联主对象
     * @param r2EntityIds          关联目标对象的主键集合，如用户关联角色的Role对象集合的主键
     * @param r2PropertyName       主对象中关联集合对象属性的名称，如User对象中定义的userR2Roles属性名
     * @param r2EntityPropertyName 被关联对象在R2关联对象定义中的属性名称，如UserR2Role中定义的role属性名
     */
    protected void updateRelatedR2s(T entity, Serializable[] r2EntityIds, String r2PropertyName, String r2EntityPropertyName) {
        try {
            List oldR2s = (List) ReflectionUtil.getFieldValue(entity, r2PropertyName);
            if (oldR2s == null) {
                oldR2s = new ArrayList();
                ReflectionUtil.setFieldValue(entity, r2PropertyName, oldR2s);
            }
            if ((r2EntityIds == null || r2EntityIds.length == 0)) {
                if (!CollectionUtil.isEmpty(oldR2s)) {
                    oldR2s.clear();
                }
            } else {
                Field r2field = ReflectionUtil.getAccessibleField(getEntityClass(), r2PropertyName);
                Class r2Class = (Class) (((ParameterizedType) r2field.getGenericType()).getActualTypeArguments()[0]);
                Field entityField = null;
                Field[] fields = r2Class.getDeclaredFields();
                for (Field field : fields) {
                    if (field.getType().equals(getEntityClass())) {
                        entityField = field;
                        break;
                    }
                }

                Field r2EntityField = ReflectionUtil.getAccessibleField(r2Class, r2EntityPropertyName);
                Class r2EntityClass = r2EntityField.getType();

                // 双循环处理需要删除关联的项目
                if (CollectionUtil.isNotEmpty(oldR2s)) {
                    List tobeDleteList = new ArrayList();
                    for (Object r2 : oldR2s) {
                        boolean tobeDlete = true;
                        for (Serializable r2EntityId : r2EntityIds) {
                            Object r2Entity = entityManager.find(r2EntityClass, r2EntityId);
                            if (ReflectionUtil.getFieldValue(r2, r2EntityPropertyName).equals(r2Entity)) {
                                tobeDlete = false;
                                break;
                            }
                        }
                        if (tobeDlete) {
                            tobeDleteList.add(r2);
                        }
                    }
                    oldR2s.removeAll(tobeDleteList);
                }

                // 双循环处理需要新增关联的项目
                for (Serializable r2EntityId : r2EntityIds) {
                    Object r2Entity = entityManager.find(r2EntityClass, r2EntityId);
                    boolean tobeAdd = true;
                    if (CollectionUtil.isNotEmpty(oldR2s)) {
                        for (Object r2 : oldR2s) {
                            if (ReflectionUtil.getFieldValue(r2, r2EntityPropertyName).equals(r2Entity)) {
                                tobeAdd = false;
                                break;
                            }
                        }
                    }
                    if (tobeAdd) {
                        Object newR2 = r2Class.newInstance();
                        ReflectionUtil.setFieldValue(newR2, r2EntityField.getName(), r2Entity);
                        ReflectionUtil.setFieldValue(newR2, entityField.getName(), entity);
                        oldR2s.add(newR2);
                    }
                }
            }
            save(entity);
        } catch (Exception e) {
            throw new ServiceException(e.getMessage(), e);
        }
    }


    /**
     * 基于Native SQL和分页(不含排序，排序直接在native sql中定义)对象查询数据集合
     *
     * @param sqlId    Native SQL(自行组装好动态条件和排序的原生SQL语句，不含order by部分)
     * @param pageable 分页(不含排序，排序直接在native sql中定义)对象
     * @return Map结构的集合分页对象
     */
    @Override
    public <S> PageInfo<S> nativeQueryPage(String sqlId, Pageable pageable, Object parameterObject, Class<S> returnClass) {
        return nQuery.findPage(sqlId, pageable, parameterObject, returnClass);
    }


    @Override
    public PageInfo<Record> nativeQueryPage(String sqlId, Pageable pageable, Object parameterObject) {
        return nQuery.findPage(sqlId, pageable, parameterObject, Record.class);
    }

    @Override
    public <S> List<S> nativeQueryList(String sqlId, Object parameterObject, Class<S> returnClass) {
        return nQuery.findList(sqlId, parameterObject, returnClass);
    }

    @Override
    public List<Record> nativeQueryList(String sqlId, Object parameterObject) {
        return nQuery.findList(sqlId, parameterObject, Record.class);
    }

    @Override
    public <S> List<S> nativeQueryLimit(String sqlId, Object parameterObject, int limit, Class<S> returnClass) {
        return nQuery.findLimit(sqlId, parameterObject, limit, returnClass);
    }

    @Override
    public List<Record> nativeQueryLimit(String sqlId, Object parameterObject, int limit) {
        return nQuery.findLimit(sqlId, parameterObject, limit);
    }

    @Override
    public <S> S nativeQueryOne(String sqlId, Object parameterObject, Class<S> returnClass) {
        return nQuery.findOne(sqlId, parameterObject, returnClass);
    }

    @Override
    public <S> List<S> nativeQueryLimit(String sqlId, Object parameterObject, Pageable pageable, Class<S> returnClass) {
        return nQuery.findLimit(sqlId, parameterObject, pageable, returnClass);
    }

    @Override
    public List<Record> nativeQueryLimit(String sqlId, Object parameterObject, Pageable pageable) {
        return nQuery.findLimit(sqlId, parameterObject, pageable);
    }

    @Override
    public List<ParallelQueryResult> parallelNativeQuery(List<ParallelQuery> parallelQueries, ParallelQueryConfig parallelConfig) {
        return nQuery.parallelQuery(parallelQueries, parallelConfig);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int executeSql(String sqlId, Object parameterObject) {
        return nQuery.executeSql(sqlId, parameterObject);
    }

    @Override
    public void detach(Object entity) {
        entityManager.detach(entity);
    }

}
